package chro_final;

import java.util.Random;

/**
 * This class basically represents a list of genes.
 * 
 * @version 1.1
 * 
 * @author Roch Dionnet
 * @see Gene
*/
public final class Chromosome {

    /**
     * Usual constructor. Creates random (valid) genes in a valid sequence. Also calculates <i>score</i>.
     * 
     * @param seed 
     *             Each seed will produce an unique chromosome.
     */
    public Chromosome(int seed) {
        
        // gene creation
        genes = new Gene[geneCount];
        
        for(int i = 0; i < geneCount; i++) {
            
            int random = (int)(Math.log((double)(seed/2*Math.pow(i, seed%6+6)+1)*seed));
            
            if(i%2==0) {
                
                genes[i] = new Gene((char) ('0'+(random%10)));
            }
          
            else {
                
                char operator = 0;
                
                switch(random%4) {
                    
                    case 0:
                        operator = '+';
                        break;
                    case 1:
                        operator = '-';
                        break;
                    case 2:
                        operator = '*';
                        break;
                    case 3:
                        operator = '/';
                        break;
                }
                
                genes[i] = new Gene(operator);
            }
        }
        
        calculateScore();
    }
    
    /**
     * Creates a chromosome by cross-breeding to others.
     * 
     * @param c1 First chromosome to be bred.
     * @param c2 Second chromosome to be bred. 
     */
    protected Chromosome(Chromosome c1, Chromosome c2) {
        
        genes = new Gene[geneCount];
        
        for(int i = 0; i < geneCount; i++) {
            
            genes[i] = new Gene(i<=geneCount/2 
                    ? c1.getGenes()[i].getValue() 
                    : c2.getGenes()[i].getValue());
        }
        
        calculateScore();
    }
    
    /**
     * Creates a new generation of chromosomes, cross-breeding two other chromosomes.
     * Generated chromosomes may mutate.
     * 
     * @param c1
     *             First chromosome to be bred.
     * @param c2
     *             Second chromosome to be bred.
     * @param childrenCount
     *             Number of children to generate.
     * @param mutationRate
     *             Chromosome mutation rate. From 0 (no mutation) to 1 (always and fully mutate).
     * @return Array of generated chromosomes.
     */
    public static Chromosome[] newGen(Chromosome c1, Chromosome c2, int childrenCount, float mutationRate) {
        
        // creer childrenCount enfant en croisant c1 et c2, puis les muter.
        
        Chromosome[] children = new Chromosome[childrenCount];
        
        for(int i = 0; i < childrenCount; i++) {
            
            children[i] = new Chromosome(c1, c2);
            children[i].mutate(mutationRate);
            // hello !
        }
        
        return children;
    }
    
    @Override
    public String toString() {
        
        String ret = "";
        for(int i = 0; i < geneCount; i++) {
            
            ret+=genes[i].getValue();
            ret+=" ";
        }
        
        return ret;
    }
    
    /**
     * Calculates the chromosome's score (fitness) if needed then returns it.
     * 
     * @return The chromosome's score.
     */
    public float getScore() {

        return score;
    }
    
    /**
     * Sets the <i>score</i> member variable. 
     */
    protected void calculateScore() {
        
        float _score = 0.f;
        char operator = '+';
        
        for(int i = 0; i<geneCount; i++) {
            
            if(i%2==0) {
                
                char operand = (char) (genes[i].getValue()-'0');
                
                switch(operator) {
                    
                    case '+':
                        _score += (float)operand;
                        break;
                    case '-':
                        _score -= (float)operand;
                        break;
                    case '*':
                        _score *= (float)operand;
                        break;
                    case '/':
                        if(operand==0) {
                            
                            _score = 0;
                            break;
                        }
                        _score /= (float)operand;
                        break;
                    default:
                        //TODO throw exception
                        break;
                }
            }

            else
                operator = genes[i].getValue();
        }
        
        this.score = _score;
    }
    
    /**
     * Modifies the genes randomly.
     * 
     * @param mutationIntensity The mutation intensity. Between 0.f (no mutation) and 1.f (complete mutation).
     */
    protected void mutate(float mutationIntensity) {
        
        Random rand = new Random();
        
        for(int i = 0; i < geneCount; i++) {
            
            if((rand.nextInt(50)+1)/49.f<=mutationIntensity) {
                
                char geneValue = genes[i].getValue();
                
                if(geneValue-'0'>=0 && geneValue-'0'<=9) {

                    geneValue = (char) ((char)(rand.nextInt(10000)+1)%10+'0');
                }
                
                else {

                    switch(rand.nextInt(100)%4) { // 42 43 45 47
                        
                        case 0:
                            geneValue = '+';
                            break;
                        case 1:
                            geneValue = '-';
                            break;
                        case 2:
                            geneValue = '*';
                            break;
                        case 3:
                            geneValue = '/';
                            break;
                    }
                }
                
                genes[i] = new Gene(geneValue);
            }
        }
        
        calculateScore();
    }
    
    /**
     * <i>genes</i> getter. Protected because intended to be used when cross-breeding chromosomes.
     * 
     * @return this.genes. 
     */
    protected Gene[] getGenes() { return this.genes; }
    
    /**
     * The chromosome's fitness. It is the result of the gene calculation.
     */
    protected float score; 
    
    /**
     * The chromosomes' gene count. Must be an odd number.
     */
    protected static int geneCount = 9;
    
    /**
     * The chromosome's data. See the program's description for detailed information.
     */
    protected Gene[] genes;
}
